/*******************************************************************\

Module: Spawning an external process that communicates with the
        main process via pipelines

Author: Philipp Ruemmer, ph_r@gmx.net

\*******************************************************************/

#include "piped_process.h"

#include <algorithm>
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>

using namespace std;

piped_processt::piped_processt(const std::string& cmd,
                               const std::vector<std::string>& arguments) {

	int in[2];
	int out[2];
	int err[2];

	if (pipe(in) == -1 || pipe(out) == -1 || pipe(err) == -1)
		throw (string("Error creating pipes to external process ") + cmd);

	proc_id = fork();

	if (proc_id == 0) {
		// child process

		close(in[1]);
		close(out[0]);
		close(err[0]);

                dup2(in[0], 0);
                dup2(out[1], 1);
                // dup2(2, err[2]);

                char** args = new char* [arguments.size() + 2];
        	args[0] = (char*)"";
        	for (unsigned int i = 0; i < arguments.size(); ++i)
        		args[i+1] = (char*)arguments[i].c_str();
        	args[arguments.size()+1] = NULL;

        	if (execvp(cmd.c_str(), args) == -1) {
        		perror(0);
        		_exit(1);
        	}

	} else if (proc_id == -1) {
		// an error occurred
		throw (string("Error creating external process ") + cmd);
	} else {
		// parent process

		close(in[0]);
		close(out[1]);
		close(err[1]);

		proc_in = in[1];
		proc_out = out[0];
		proc_err = err[0];

	}

}

piped_processt::~piped_processt() {
	cleanup();
}

void piped_processt::cleanup() {
	close(proc_in);
	close(proc_out);
	close(proc_err);
	proc_id = 0;
}

/**
 * Write some data to the process
 */
void piped_processt::write(const std::string& data) {

	unsigned int offset = 0;
	ssize_t written = 0;

	do {
		written = ::write(proc_in, data.c_str() + offset, data.size() - offset);
		if (written == -1)
	    	throw (string("Error writing to process: ") + strerror(errno));
		offset += written;
	} while (offset < data.size());

}

/**
 * Read a line of data from the external process
 *
 * (Currently implementation is shamelessly copied)
 */
std::string piped_processt::read_line() {

	// Do the real reading from fd until buffer has '\n'.
	string::iterator pos;
	while ((pos = find (in_buffer.begin(), in_buffer.end(), '\n')) == in_buffer.end ()) {
		char buf [1025];
	    const ssize_t n = read (proc_out, buf, 1024);

	    if (n == 0) {
	    	// might be that the subprocess has already terminated
	    	int status;
	    	const pid_t w = waitpid(proc_id, &status, WNOHANG);
	    	if (w != 0) {
	    		// indeed, the process has terminated
	    		cleanup();
		    	throw (string("Subprocess terminated unexpectedly"));
	    	}
	    } else if (n == -1) {    // handle errors
	    	in_buffer = "";
	    	throw (string("Error reading from process: ") + strerror(errno));
	    }

	    buf[n] = 0;
	    in_buffer += buf;
	  }

	  // Split the buffer around '\n' found and return first part.
	  const string res = string (in_buffer.begin(), pos);
	  in_buffer = string (pos + 1, in_buffer.end());

	  return res;

}
